package besta.moon.engine.selector.result;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

import besta.moon.engine.selector.DataSource;
import besta.moon.engine.table.Column;
import besta.moon.engine.table.Columns;
import besta.moon.sql.Expression;
import besta.moon.sql.datatype.MutableNumeric;
import besta.moon.sql.parser.SQLTokenizer;
import besta.moon.MoonException;
import besta.moon.MoonCode;
import besta.moon.engine.View;
import besta.moon.sql.expression.ExpressionValue;

public class MemoryResult extends DataSource {

    protected ExpressionValue[] currentRow;
    private final Columns columns = new Columns();
    private int rowIdx = -1;
    private List<ExpressionValue[]> rowList = new ArrayList<>();

    public MemoryResult() {
    }

    public MemoryResult(Object[][] data, int colCount) throws SQLException {
        for (int c = 0; c < colCount; c++) {
            Column column = new Column();
            column.setDataType(SQLTokenizer.NULL);
            columns.add(column);
        }
        for (Object[] row : data) {
            ExpressionValue[] rowValues = new ExpressionValue[row.length];
            addRow(rowValues);
            for (int c = 0; c < colCount; c++) {
                ExpressionValue expr = rowValues[c] = new ExpressionValue();
                expr.set(row[c], -1);
                Column column = columns.get(c);
                if (expr.getDataType() != SQLTokenizer.NULL) {
                    column.setDataType(expr.getDataType());
                }
                if (expr.getPrecision() > column.getPrecision()) {
                    column.setPrecision(expr.getPrecision());
                }
            }
        }
    }

    protected final void addRow(ExpressionValue[] row) {
        rowList.add(row);
    }

    public final Column getColumn(int colIdx) {
        return columns.get(colIdx);
    }

    protected final void addColumn(Column column) {
        columns.add(column);
    }

    @Override
    public final boolean isScrollable() {
        return true;
    }

    @Override
    public final void beforeFirst() {
        rowIdx = -1;
        currentRow = null;
    }

    @Override
    public final boolean isBeforeFirst() {
        return rowIdx < 0 || rowList.isEmpty();
    }

    @Override
    public final boolean isFirst() {
        return rowIdx == 0 && currentRow != null;
    }

    @Override
    public final boolean first() {
        rowIdx = 0;
        return move();
    }

    @Override
    public final boolean previous() {
        if (rowIdx-- < 0) {
            rowIdx = -1;
        }
        return move();
    }

    @Override
    public final boolean next() {
        rowIdx++;
        return move();
    }

    @Override
    public final boolean last() {
        rowIdx = rowList.size() - 1;
        return move();
    }

    @Override
    public final boolean isLast() {
        return rowIdx == rowList.size() - 1 && currentRow != null;
    }

    @Override
    public final boolean isAfterLast() {
        return rowIdx >= rowList.size() || rowList.size() == 0;
    }

    @Override
    public final void afterLast() {
        rowIdx = rowList.size();
        currentRow = null;
    }

    @Override
    public final boolean absolute(int row) throws SQLException {
        if (row == 0) {
            throw MoonException.create(MoonCode.ROW_0_ABSOLUTE);
        }
        rowIdx = (row > 0) ? Math.min(row - 1, rowList.size()) : Math.max(row + rowList.size(), -1);
        return move();
    }

    @Override
    public final boolean relative(int rows) {
        if (rows == 0) {
            return (currentRow != null);
        }
        rowIdx = Math.min(Math.max(rowIdx + rows, -1), rowList.size());
        return move();
    }

    @Override
    public final int getRow() {
        return currentRow == null ? 0 : rowIdx + 1;
    }

    @Override
    public final long getRowPosition() {
        return rowIdx;
    }

    @Override
    public final void setRowPosition(long rowPosition) throws Exception {
        rowIdx = (int) rowPosition;
        move();
    }

    @Override
    public final boolean rowInserted() {
        return false;
    }

    @Override
    public final boolean rowDeleted() {
        return false;
    }

    @Override
    public void nullRow() {
        throw new Error();
    }

    @Override
    public void noRow() {
        currentRow = null;
    }

    final private boolean move() {
        if (rowIdx < rowList.size() && rowIdx >= 0) {
            currentRow = (ExpressionValue[]) rowList.get(rowIdx);
            return true;
        }
        currentRow = null;
        return false;
    }

    @Override
    public boolean isNull(int colIdx) throws Exception {
        return get(colIdx).isNull();
    }

    @Override
    public boolean getBoolean(int colIdx) throws Exception {
        return get(colIdx).getBoolean();
    }

    @Override
    public int getInt(int colIdx) throws Exception {
        return get(colIdx).getInt();
    }

    @Override
    public long getLong(int colIdx) throws Exception {
        return get(colIdx).getLong();
    }

    @Override
    public float getFloat(int colIdx) throws Exception {
        return get(colIdx).getFloat();
    }

    @Override
    public double getDouble(int colIdx) throws Exception {
        return get(colIdx).getDouble();
    }

    @Override
    public long getMoney(int colIdx) throws Exception {
        return get(colIdx).getMoney();
    }

    @Override
    public MutableNumeric getNumeric(int colIdx) throws Exception {
        return get(colIdx).getNumeric();
    }

    @Override
    public Object getObject(int colIdx) throws Exception {
        return get(colIdx).getObject();
    }

    @Override
    public String getString(int colIdx) throws Exception {
        return get(colIdx).getString();
    }

    @Override
    public byte[] getBytes(int colIdx) throws Exception {
        return get(colIdx).getBytes();
    }

    @Override
    public int getDataType(int colIdx) {
        return columns.get(colIdx).getDataType();

    }

    @Override
    public final View getTableView() {
        return null;
    }

    final void deleteRow() throws Exception {
        throw MoonException.create(MoonCode.RSET_READONLY);
    }

    final void updateRow(Expression[] updateValues) throws Exception {
        throw MoonException.create(MoonCode.RSET_READONLY);
    }

    final void insertRow(Expression[] updateValues) throws Exception {
        throw MoonException.create(MoonCode.RSET_READONLY);
    }

    private Expression get(int colIdx) throws Exception {
        if (currentRow == null) {
            throw MoonException.create(MoonCode.ROW_NOCURRENT);
        }
        return currentRow[colIdx];
    }

    public int getRowCount() {
        return rowList.size();
    }

    public void execute() throws Exception {
        rowList.clear();
    }
}
